/*
 * File     : internal_platform_PlatformUtilities_linux.c
 * Created  : 28 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "../common.h"
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <langinfo.h>

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    getPlatformGroup
 * Signature: ()Lcom/googlecode/dni/platform/PlatformGroup;
 */
JNIEXPORT jobject JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_getPlatformGroup(
    JNIEnv* env,
    jclass clazz )
{
    jclass platformGroupClass;
    jfieldID field;

    platformGroupClass = (*env)->FindClass( env, "com/googlecode/dni/platform/PlatformGroup" );
    if ( platformGroupClass == NULL )
    {
       return NULL;
    }
    field = (*env)->GetStaticFieldID( env, platformGroupClass, "LINUX", "Lcom/googlecode/dni/platform/PlatformGroup;" );
    if ( field == NULL )
    {
       return NULL;
    }
    return (*env)->GetStaticObjectField( env, platformGroupClass, field );
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    getPlatformType
 * Signature: ()Lcom/googlecode/dni/platform/PlatformType;
 */
JNIEXPORT jobject JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_getPlatformType(
    JNIEnv* env,
    jclass clazz )
{
    char* name;
    jclass platformTypeClass;
    jfieldID field;

#if defined(__i386__)
    name = "X86";
#elif defined(__x86_64__)
    name = "X86_64";
#else
#error Unknown platform
#endif

    platformTypeClass = (*env)->FindClass( env, "com/googlecode/dni/platform/LinuxPlatformType" );
    if ( platformTypeClass == NULL )
    {
       return NULL;
    }
    field = (*env)->GetStaticFieldID( env, platformTypeClass, name, "Lcom/googlecode/dni/platform/LinuxPlatformType;" );
    if ( field == NULL )
    {
       return NULL;
    }
    return (*env)->GetStaticObjectField( env, platformTypeClass, field );
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    getNativeLocale
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jobject JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_getNativeLocale(
    JNIEnv* env,
    jclass clazz )
{
    char* charset;

    charset = nl_langinfo( CODESET );
    if ( charset == NULL )
    {
        return NULL;
    }

    // it's a bit evil, but we assume that the string is ASCII, and valid
    // ASCII is hence valid UTF-8 :-D
    return (*env)->NewStringUTF( env, charset );
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    getPageSize
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_getPageSize(
    JNIEnv* env,
    jclass clazz )
{
    return getpagesize();
}

static int protFlags( jint flags )
{
    int prot = PROT_READ;
    if ( flags & 1 )
    {
        prot |= PROT_EXEC;
    }
    if ( flags & 2 )
    {
        prot |= PROT_WRITE;
    }
    return prot;
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    allocatePages
 * Signature: (II)J
 */
JNIEXPORT jlong JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_allocatePages(
    JNIEnv* env,
    jclass clazz,
    jint size,
    jint flags )
{
    void* ptr;
    int prot = protFlags( flags );

    ptr = mmap( NULL, (size_t) size, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 );

    // yeah, please don't allocate memory at zero... that's INSANE
    if ( ptr == MAP_FAILED || ptr == NULL )
    {
        return 0;
    }

    return POINTER_TO_JLONG( ptr );
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    protectPages
 * Signature: (JII)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_protectPages(
    JNIEnv* env,
    jclass clazz,
    jlong address,
    jint size,
    jint flags )
{
    void* ptr = JLONG_TO_POINTER( address );
    int prot = protFlags( flags );
    int result;

    result = mprotect( ptr, (size_t) size, prot );

    if ( result != 0 )
    {
        return errno;
    }

    return 0;
}

/*
 * Class:     com_googlecode_dni_internal_platform_PlatformUtilities
 * Method:    unallocatePages
 * Signature: (JI)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_platform_PlatformUtilities_unallocatePages(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jint size )
{
    int result;
    
    result = munmap( JLONG_TO_POINTER( pointer ), (size_t) size );
    
    if ( result != 0 )
    {
        return errno; 
    }
    return 0;
}
